package com.jh.clockdemo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

public class ClockView extends View implements ViewTreeObserver.OnGlobalLayoutListener, DefaultLifecycleObserver {

    public static final String TAG = "ClockView";
    private Context mContext;
    private Path mPath;
    private Paint mPaint;
    private RectF mRectF;
    private float rootWidth, rootHeight;
    private float mHourDeg;
    private float mMinuteDeg;
    private float mSecondsDeg;
    private float mMillsSecondsDeg;
    private int mHourLineWidth;
    private int mMinuteLineWidth;
    private int mSecondsLineWidth;
    private int mShadowRadius;
    private int mHourLinePaddingBottom, mHourLinePaddingTop;
    private int mMinuteLinePaddingBottom, mMinuteLinePaddingTop;
    private int mSecondsLinePaddingBottom, mSecondsLinePaddingTop;
    private int mHourTextPaddingTop;
    private int mTickLinePaddingBottom, mTickLinePaddingTop;

    ValueAnimator scaleVal = ValueAnimator.ofFloat(1f, 0.9f);
    private String mTimeZoneId;

    SimpleDateFormat mZoneControl = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
    SimpleDateFormat mHourFormat = new SimpleDateFormat("HH", Locale.CHINA);
    SimpleDateFormat mMinuteFormat = new SimpleDateFormat("mm", Locale.CHINA);
    SimpleDateFormat mSecondsFormat = new SimpleDateFormat("ss", Locale.CHINA);
    SimpleDateFormat mMillsSecondsFormat = new SimpleDateFormat("SSS", Locale.CHINA);

    ScheduledExecutorService mExecutorService = Executors.newScheduledThreadPool(1);
    ScheduledFuture<?> mScheduledFuture;

    TimeTickRunnable mTimeTickRunnable;

    ValueAnimator mHourDegAnim = new ValueAnimator();
    ValueAnimator mMinuteDegAnim = new ValueAnimator();
    ValueAnimator mSecondDegAnim = new ValueAnimator();

    boolean isBgFlag;
    boolean IS_SECOND_ZHEN_MILLS = true;
    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            invalidate();
            return false;
        }
    });

    public ClockView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public ClockView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0 , 0);
    }

    public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ClockView, defStyleAttr, defStyleRes);
        String timeZoneAttr = typedArray.getString(R.styleable.ClockView_time_zone);
        setTimeZone(timeZoneAttr);
        typedArray.recycle();
        init();
    }

    private void init(){
        mHourLineWidth = getResources().getDimensionPixelSize(R.dimen.hour_line_width);
        mMinuteLineWidth = getResources().getDimensionPixelSize(R.dimen.minute_line_width);
        mSecondsLineWidth = getResources().getDimensionPixelSize(R.dimen.seconds_line_width);
        mShadowRadius = getResources().getDimensionPixelSize(R.dimen.shadow_radius);
        mHourLinePaddingTop = getResources().getDimensionPixelSize(R.dimen.hour_line_padding_top);
        mMinuteLinePaddingTop = getResources().getDimensionPixelSize(R.dimen.minute_line_padding_top);
        mSecondsLinePaddingTop = getResources().getDimensionPixelSize(R.dimen.seconds_line_padding_top);
        mHourTextPaddingTop = getResources().getDimensionPixelSize(R.dimen.hour_text_padding_top);
        mTickLinePaddingBottom = getResources().getDimensionPixelSize(R.dimen.tick_line_padding_bottom);
        mTickLinePaddingTop = getResources().getDimensionPixelSize(R.dimen.tick_line_padding_top);

        scaleVal.setDuration(200);
        scaleVal.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                setScaleX(animatedValue);
                setScaleY(animatedValue);
            }
        });

        mPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        getViewTreeObserver().addOnGlobalLayoutListener(this);


        mHourDegAnim.setDuration(1000);
        mHourDegAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                mHourDeg = (float) animation.getAnimatedValue();
            }
        });
        mMinuteDegAnim.setDuration(5000);
        mMinuteDegAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                mMinuteDeg = (float) animation.getAnimatedValue();
                if (mMinuteDeg < getHMSDeg(mTimeZoneId)[1]){
                    mMinuteDegAnim.setFloatValues(mMinuteDeg, getHMSDeg(mTimeZoneId)[1]);
                }
            }
        });
        mSecondDegAnim.setDuration(5000);
        mSecondDegAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                mSecondsDeg = (float) animation.getAnimatedValue();
                if (mSecondsDeg < getHMSDeg(mTimeZoneId)[2]){
                    mSecondDegAnim.setFloatValues(mSecondsDeg, getHMSDeg(mTimeZoneId)[2]);
                }
                invalidate();
            }
        });
        mSecondDegAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mScheduledFuture = mExecutorService.scheduleWithFixedDelay(mTimeTickRunnable, 0, 15, TimeUnit.MILLISECONDS);
            }
        });

        mHourDeg = getHMSDeg(mTimeZoneId)[0];
        mMinuteDeg = getHMSDeg(mTimeZoneId)[1];
        mSecondsDeg = getHMSDeg(mTimeZoneId)[2];

        mHourDegAnim.setFloatValues(0, mHourDeg);
        mMinuteDegAnim.setFloatValues(0, mMinuteDeg);
        mSecondDegAnim.setFloatValues(0, mSecondsDeg);

        //这里一秒中更新下ui
        mTimeTickRunnable = new TimeTickRunnable();
        if (!IS_SECOND_ZHEN_MILLS){
            mScheduledFuture = mExecutorService.scheduleWithFixedDelay(mTimeTickRunnable, 0, 100, TimeUnit.MILLISECONDS);
        }else{
            mHourDegAnim.start();
            mMinuteDegAnim.start();
            mSecondDegAnim.start();
        }
    }

    class TimeTickRunnable implements Runnable{
        @Override
        public void run() {
            float[] hmsDeg = getHMSDeg(mTimeZoneId);
            mHourDeg = hmsDeg[0];
            mMinuteDeg = hmsDeg[1];
            mSecondsDeg = hmsDeg[2];

            mHandler.sendEmptyMessage(1);
        }
    }

    private float[] getHMSDeg(String timeZoneId){
        float[] d = new float[3];
        long date = 0;
        if (!TextUtils.isEmpty(timeZoneId)){
            mZoneControl.setTimeZone(TimeZone.getTimeZone(timeZoneId));
            String currZoneTime = mZoneControl.format(System.currentTimeMillis());
            mZoneControl.setTimeZone(TimeZone.getDefault());
            try {
                Date parse = mZoneControl.parse(currZoneTime);
                if (parse != null){
                    date = parse.getTime();
                }
            } catch (ParseException e) {
                Log.d(TAG, "run: parse exception " + e);
            }
        }else{
            date = System.currentTimeMillis();
        }

        String hour = mHourFormat.format(date);
        String minute = mMinuteFormat.format(date);
        String seconds = mSecondsFormat.format(date);
        String millsSeconds = mMillsSecondsFormat.format(date);
        d[0] = 1.0f * 360 / 60 / 60 / 12 * (Float.parseFloat(seconds) + Float.parseFloat(minute) * 60 + Float.parseFloat(hour) * 3600);
        d[1]  = 1.0f * 360 / 60 / 60 * (Float.parseFloat(seconds) + Float.parseFloat(minute) * 60);
        if (IS_SECOND_ZHEN_MILLS){
            d[2]  = 1.0f * 360 / 60000 * (Float.parseFloat(seconds) * 1000 + Float.parseFloat(millsSeconds));
        }else{
            d[2]  = 1.0f * 360 / 60 * (Float.parseFloat(seconds));
        }
        return d;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        rootWidth = getWidth();
        rootHeight = getHeight();
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        int count = canvas.saveLayer(0, 0, rootWidth, rootHeight, mPaint);
        if (!isBgFlag) createClockBgBitmap(canvas, rootWidth, rootHeight);
        createClockHourBitmap(canvas, rootWidth, rootHeight);
        createClockMinuteBitmap(canvas, rootWidth, rootHeight);
        createClockSecondBitmap(canvas, rootWidth, rootHeight);
    }

    @Override
    public void onGlobalLayout() {
        mRectF = new RectF(10, 10, rootWidth - 10, rootHeight - 10);
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    //画时钟背景
    int[] colors = {getResources().getColor(R.color.clock_bg_color, null), getResources().getColor(R.color.clock_bg_color, null), getResources().getColor(R.color.clock_shader_color, null)};
    float[] positions = {0f, 0.95f, 1f};

    private void createClockBgBitmap(Canvas canvas, float width, float height){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(5);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(getResources().getColor(R.color.clock_bg_color, null));
        //paint.setShadowLayer(10, 0, 0, getResources().getColor(R.color.clock_shadow_color, null));

        RadialGradient radialGradient = new RadialGradient(
                getWidth()/2f, getHeight()/2f, getHeight()/2f,
                colors,
                positions,
                Shader.TileMode.CLAMP
        );
        paint.setShader(radialGradient);

        canvas.drawCircle(width / 2, height / 2, width / 2 - 10, paint);
        paint.setShader(null);
        paint.setShadowLayer(10, 0, 0, getResources().getColor(R.color.clock_bg_color, null));
        paint.setStyle(Paint.Style.STROKE);
        for (int i = 0; i < 60; i++) {
            if ( i % 5 == 0){
                paint.setColor(getResources().getColor(R.color.clock_zhen_color, null));
                canvas.save();
                canvas.rotate(6 * i, width / 2, height / 2);
                paint.setStrokeWidth(3);
                paint.setStyle(Paint.Style.FILL_AND_STROKE);
                paint.setTextSize(50);
                Typeface boldTypeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
                paint.setTypeface(boldTypeface);
                paint.setTypeface(boldTypeface);
                paint.setFakeBoldText(true);
                paint.setTextAlign(Paint.Align.CENTER);
                if (i == 0){
                    canvas.drawText( "12", width / 2, mHourTextPaddingTop, paint);
                }else{
                    canvas.drawText(i / 5 + "", width / 2, mHourTextPaddingTop, paint);
                }
                paint.setStrokeWidth(5);
                paint.setStyle(Paint.Style.STROKE);
                canvas.drawLine( width / 2, mTickLinePaddingBottom, width / 2, mTickLinePaddingTop, paint);
                canvas.restore();
            }else{
                paint.setColor(Color.GRAY);
                canvas.save();
                canvas.rotate(6 * i, width / 2, height / 2);
                canvas.drawLine( width / 2, mTickLinePaddingBottom, width / 2, mTickLinePaddingTop, paint);
                canvas.restore();
            }
        }
    }

    //画时针
    private void createClockHourBitmap(Canvas canvas, float width, float height){
        mPaint.setColor(getResources().getColor(R.color.clock_zhen_color, null));
        mPaint.setStrokeWidth(mHourLineWidth);
        mPaint.setShadowLayer(mShadowRadius, 0, 0, Color.BLACK);
        canvas.save();
        canvas.rotate(mHourDeg, width / 2, height / 2);
        canvas.drawLine(width / 2,  height / 2, width / 2, mHourLinePaddingTop, mPaint);
        canvas.restore();
    }

    //画分针
    private void createClockMinuteBitmap(Canvas canvas, float width, float height){
        mPaint.setColor(getResources().getColor(R.color.clock_zhen_color, null));
        mPaint.setStrokeWidth(mMinuteLineWidth);
        mPaint.setShadowLayer(mShadowRadius, 0, 0, Color.BLACK);
        canvas.save();
        canvas.rotate(mMinuteDeg, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2, width / 2, mMinuteLinePaddingTop, mPaint);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(width / 2, height / 2, 15, mPaint);
        canvas.restore();
    }

    //画秒针!
    private void createClockSecondBitmap(Canvas canvas, float width, float height){
        mPaint.setColor(getResources().getColor(R.color.blue, null));
        mPaint.setStrokeWidth(mSecondsLineWidth);
        mPaint.setShadowLayer(10, 0, 0, getResources().getColor(R.color.blue_shadow, null));
        canvas.save();
        canvas.rotate(mSecondsDeg, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 + 40, width / 2, mSecondsLinePaddingTop, mPaint);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(width / 2, height / 2, 12, mPaint);
        mPaint.setColor(getResources().getColor(R.color.clock_bg_color, null));
        canvas.drawCircle(width / 2, height / 2, 8, mPaint);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(getResources().getColor(R.color.blue, null));
        mPaint.setStrokeWidth(12);
        canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 + 100, mPaint);
        canvas.restore();
    }

    public void setTimeZone(String id){
        mTimeZoneId = id;
    }

    @Override
    public void onResume(@NonNull LifecycleOwner owner) {
        Log.d(TAG, "onResume: ");
        if (mScheduledFuture != null && mScheduledFuture.isCancelled()){
            if (!IS_SECOND_ZHEN_MILLS){
                mExecutorService.scheduleWithFixedDelay(mTimeTickRunnable, 0, 100, TimeUnit.MILLISECONDS);
            }else{
                mExecutorService.scheduleWithFixedDelay(mTimeTickRunnable, 0, 15, TimeUnit.MILLISECONDS);

            }
        }
    }

    @Override
    public void onPause(@NonNull LifecycleOwner owner) {
        Log.d(TAG, "onPause: ");
        if (mScheduledFuture != null){
            mScheduledFuture.cancel(true);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                scaleVal.start();
                break;
            case MotionEvent.ACTION_UP:
                scaleVal.reverse();
                break;
        }
        return true;
    }
}
